JavaScript实现拼图游戏

作者:陈广
日期:2018-4-11


本想这个程序用 TypeScript 来实现的,但突然发现。只有两个事件方法,真没必要用 TypeScript。即使用了,代码也只多不少,因为 JavaScript 中的两个不同类型间是可以进行对比的。在需要用到类的地方我们再使用 TypeScript。学习之前先玩玩这个游戏:

HTML 中实现按钮

拼图游戏和拼数字游戏其实是一样的,思路无任何不同,只是实现上稍有区别。这一次我们先将可移动的按钮在 HTML 中创建,之后再演示如何在 JavaScript 中拼凑 HTML,动态插入 Div 中。

HTML 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>拼数字游戏</title>
    <link rel="stylesheet" href="NumberGame.css">
</head>
<body>
    <div id="Container">
        <img class="backgroundImg" src="img/1.png" onclick="Btn_Click(0)"/>
        <img class="backgroundImg" src="img/2.png" onclick="Btn_Click(1)"/>
        <img class="backgroundImg" src="img/3.png" onclick="Btn_Click(2)"/>
        <img class="backgroundImg" src="img/4.png" onclick="Btn_Click(3)"/>
        <img class="backgroundImg" src="img/5.png" onclick="Btn_Click(4)"/>
        <img class="backgroundImg" src="img/6.png" onclick="Btn_Click(5)"/>
        <img class="backgroundImg" src="img/7.png" onclick="Btn_Click(6)"/>
        <img class="backgroundImg" src="img/8.png" onclick="Btn_Click(7)"/>
        <img class="backgroundImg" src="img/9.png" onclick="Btn_Click(8)"/>
    </div>
    <button id="Button">开始游戏</button>
</body>
<script src="NumberGame.js"></script>
</html>

因为要存放图片,这里并没有使用按钮控件,而是直接使用img。我们看到在Btn_Click方法中加入了一个数字参数,主要用于方便在 JavaScript 中判断点击的是哪一个按钮。其实也可以不用加入这个参数,直接在src属性里把相应数字提取出来进行判断。这里更多是为了演示如何在鼠标事件里传递参数。

CSS 代码

body {
	text-align: center;
	background-color: #ddd;
}

#Container {
	display: flex;
	flex-wrap: wrap;
	position: relative;
	width: 240px;
	height: 239px;
	margin: 0 auto;
	margin-top: 20px;
	border: 5px solid darkgoldenrod;
	border-radius: 5px;
}

.backgroundImg {
	width: 80px;
	height: 80px;
	box-sizing: border-box;
	border: 2px solid darkgoldenrod;
}

#Button {
	margin-top: 20px;
	padding: 10px 20px;
	background: #A2B598;
	background: -webkit-gradient(linear, left top, left bottom, from(#BDD1B4), to(#A2B598));
	background: -moz-linear-gradient(-90deg, #BDD1B4, #A2B598);
	filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr='#BDD1B4', EndColorStr='#A2B598');
}

#Button:hover {
	background: #BDD1B4;
	background: -webkit-gradient(linear, left top, left bottom, from(#A2B598), to(#BDD1B4));
	background: -moz-linear-gradient(-90deg, #A2B598, #BDD1B4);
	filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr='#A2B598', EndColorStr='#BDD1B4');
}

#Button:active {
	background: #A2B598;
}

JavaScript 代码

var imgList; //装按钮的数组
var cover = 0;
//窗体载入事件
window.onload = function () {
    var container = document.getElementById("Container");
    imgList = container.children;
    //给开始游戏按钮绑定事件
    document.getElementById("Button").addEventListener("click", PlayGame);
}

//随机函数low表示最小随机值,high表示最大随机值
function randomBetween(low, high) {
    return Math.floor(Math.random() * (high - low + 1) + low);
}
//开始游戏方法,用于绑定开始游戏按钮
function PlayGame() {
    console.log(cover);
    imgList[cover].style.opacity = "1";
    SetBorder("2");
    var nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    for (var i = 0; i < 8; i++) {
        var rmNum = randomBetween(i, 8);
        var temp = nums[i];
        nums[i] = nums[rmNum];
        nums[rmNum] = temp;
    }
    for (var i = 0; i < 9; i++) {
        imgList[i].src = "img/" + nums[i] + ".png";
    }
    cover = randomBetween(0, 8);
    imgList[cover].style.opacity = "0";
}
//单击数字时的事件
function Btn_Click(index) {
    if (((Math.abs(index - cover) == 1) &&
            (Math.floor(index / 3) == Math.floor(cover / 3))) ||
            (Math.abs(index - cover) == 3)) {
        var temp = imgList[cover].src;
        imgList[cover].src = imgList[index].src
        imgList[index].src = temp;
        imgList[cover].style.opacity = "1";
        imgList[index].style.opacity = "0";
        cover = index;
    }
    //判定是否游戏结束
    for (var i = 0; i < 9; i++) {
        var str = imgList[i].src;
        if (str[str.length - 5] != i + 1) {
            break;
        }
        if (i == 8) {
            imgList[cover].style.opacity = "1";
            SetBorder("0");
            alert("闯关成功!");
        }
    }
}
//设置图片边框
function SetBorder(width) {
    for (var i = 0; i < 9; i++) {
        imgList[i].style.border = width + "px solid darkgoldenrod";
    }
}

由于 div 的children已经将 9 个按钮存放在数组中了,所以可以直接使用,无需另外创建数组存放。为此我还改了算法,改完后惊喜地发现,原来代码还可以更少。简而言之,之前使用的是二维数组实现,现在改用一维数组实现了。看来之前是思想是走弯路了。

动态插入 HTML

对于这个游戏而言,以上实现其实是最完美的了。下面换一种方式来实现,同样的 HTML,用 JavaScript 动态插入。之所以要这样做,是因为这是实现目录树的关键。

HTML 代码

将 HTML 改为:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>拼数字游戏</title>
    <link rel="stylesheet" href="NumberGame.css">
</head>
<body>
    <div id="Container"></div>
    <button id="Button">开始游戏</button>
</body>
<script src="NumberGame.js"></script>
</html>

现在看,HTML 代码少了很多,清爽了。

JavaScript 代码

JavaScript 这边,只需更改window.onload方法,其它不用动:

window.onload = function () {
    var container = document.getElementById("Container");
    var str = "";
    for (var i = 0; i < 9; i++) {
        str += '<img class="backgroundImg" src="img/' + (i + 1) +
            '.png" onclick="Btn_Click(' + i + ')"/>'
    }
    container.innerHTML=str;
    imgList = container.children;
    //给开始游戏按钮绑定事件
    document.getElementById("Button").addEventListener("click", PlayGame);
}

我们看到,这次通过一个循环将所有img的 HTML 拼凑出来,然后通过innerHTML属性注入到 div 中。效果和在 HTML 中写一模一样。

注入 HTML 的方法一下就写完了,比我想象的快多了。突然发现,目录树也不是那么难,惊喜啊!